package com.googlecode.cswish.struts.spring;

import java.lang.reflect.Method;

import javax.annotation.Resource;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.cglib.proxy.Enhancer;

import com.googlecode.cswish.Config;
import com.googlecode.cswish.annotation.Protected;
import com.googlecode.cswish.struts.Invoker;
import com.googlecode.cswish.struts.Routing;
import com.googlecode.cswish.struts.hibernate.HibernateConfiguration;
import com.googlecode.cswish.util.FrameConstant;
import com.opensymphony.xwork2.Action;
import com.opensymphony.xwork2.ValidationAware;
import com.opensymphony.xwork2.util.ClassLoaderUtil;

/**
 * Wrap service object and model object as an action object 
 * 
 * <p> conversion rule:<br>
 * 1. Parameter --> property<br>
 * 2. Method --> property (it stores the method result)
 * 3. Use com.opensymphony.xwork2.Result as return value
 * 
 * @author Jerry.feng Date: 27/08/2008
 */
public class ActionFactoryBean implements FactoryBean {
	
	private static final Log logger = LogFactory.getLog(ActionFactoryBean.class);

	protected static final Class<?>[] INTERFACES = new Class[] {
			ValidationAware.class
	};
	
	@Resource(name="cswish.dummyBean" )
	private Object DUMMY_ACTION;
	
	@Resource
	private HibernateConfiguration hibernateConfiguration;
	
	@Resource
	private DFactory dfactory;
	
	@Resource
	private ObjectFactory objectFactory;
	
	@Resource
	private BeanIntrospector beanIntrospector;
	
	@Resource
	private Routing routing;
	
	@Resource
	private Config config;
	
	/**
	 * action name
	 */
	private String action;

	private Invoker invoker; 
	
	/**
	 * cache action type & action method
	 */
	private Class<?> serviceType;
	private Method serviceMethod;
	private Class<?> objectType;
	
	public String getAction() {
		return action;
	}

	public void setAction(String actionName) {
		this.action = actionName;
		
		this.invoker = routing.action2Invoker(actionName);
		// reset cache
		this.objectType = null;
		this.serviceMethod = null;
	}

	/**
	 * Find the first matched method
	 * 
	 * @param clazz - the class to search
	 * @param method - the name to match
	 * @return matched method
	 */
	private Method getMethod(Class<?> clazz, String method) {
		Method[] methods = clazz.getMethods();
		Method found = null;
		for (Method m : methods) {
			if (m.getAnnotation(Protected.class) == null) {
				if (method.equals(m.getName())) {
					found = m;
					break;
				}
			}
		}
		
		// use DFactory create a new method according to current method interface template
		if (found == null && method.startsWith(FrameConstant.ACTION_PREPARE)) {
			found = getMethod(clazz, 
					method.substring(FrameConstant.ACTION_PREPARE.length()));
		}
		return found;
	}
	
	/**
	 * Create proxy object
	 */
	@Override
	public Object getObject() throws Exception {
		if (objectType == null) {
			// initialize
			init();
			
			if (serviceMethod == null) {
				return DUMMY_ACTION;
			}
			
			// 3. create proxy
			boolean callMethod = serviceMethod.getName().equals(invoker.methodName);
			if (logger.isDebugEnabled()) {
				logger.debug(serviceMethod.getDeclaringClass().getName() + '.' + serviceMethod.getName()
						+ " is called");
			}
			
			objectType = dfactory.convert2Class(serviceType, serviceMethod, invoker.toString(),
					config.getBaseAction(), "execute", callMethod, Action.SUCCESS
			);
		}
		Object instance = objectType.newInstance();
		return instance;
	}

	@Override
	public Class getObjectType() {
		return objectType;
	}
	
	protected void init() {
		// 1. check method
		String methodName = invoker.methodName;
		
		serviceType = DUMMY_ACTION.getClass();
		if (methodName != null) {
			// 2. load class: action --> service --> generic
			// 2.1 try to load action
			Class<?> theObjectType = null;
			if (action != null) {
				try {
					theObjectType = ClassLoaderUtil.loadClass(invoker.toString(), this.getClass());
					serviceMethod = getMethod(theObjectType, methodName);
				} catch (ClassNotFoundException ex) {
				}
			} else {
				action = DUMMY_ACTION.getClass().getName() + invoker.separator + methodName;
			}
			
			// 2.2 try to load service
			if (theObjectType == null || serviceMethod == null) {
				String serviceName = null;
				try {
					String simpleName = beanIntrospector.getSimpleName(invoker.toString());
					serviceName = beanIntrospector.getServiceName(simpleName);
					theObjectType = ClassLoaderUtil.loadClass(serviceName, this.getClass());
					serviceMethod = getMethod(theObjectType, methodName);
				} catch (ClassNotFoundException ex) {
					if (logger.isDebugEnabled()) {
						logger.debug("Can't find service class:" + serviceName);
					}
				}
			}
			
			// 2.3. try to find from the generic service
			if (theObjectType == null || serviceMethod == null) {
				String simpleName = beanIntrospector.getSimpleName(invoker.toString());
				String modelName = beanIntrospector.getModelName(simpleName);
				if (beanIntrospector.getModelInfo(modelName) != null) {
					try {
						theObjectType = objectFactory.getClassInstance(GenericService.class.getName());
						if (Enhancer.isEnhanced(theObjectType)) {
							theObjectType = theObjectType.getSuperclass();
						} else {
							theObjectType = theObjectType.getClass();
						}
						serviceMethod = getMethod(theObjectType, methodName);
					} catch (ClassNotFoundException ex) {
						logger.error("Can't find generic service", ex);
					}
				}
			}
			
			// no matched method
			if (serviceMethod == null) {
				serviceType = DUMMY_ACTION.getClass();
				if (logger.isDebugEnabled()) {
					logger.debug("No matched Action/Service/Model for request: " + invoker + ", user Dummy action");
				}
			} else {
				serviceType = theObjectType;
			}
		}
	}

	@Override
	public boolean isSingleton() {
		// action need keep the UI status, so it's NOT singleton
		return false;
	}
}